// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/url_request/url_request_filter.h"

#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/stl_util.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_job_factory_impl.h"

namespace net {

namespace {

    // When adding interceptors, DCHECK that this function returns true.
    bool OnMessageLoopForInterceptorAddition()
    {
        base::MessageLoop* message_loop = base::MessageLoop::current();
        // Return true if called on a MessageLoopForIO or if there is no MessageLoop.
        // Checking for a MessageLoopForIO is a best effort at determining whether the
        // current thread is a networking thread.  Allowing cases without a
        // MessageLoop is required for some tests where there is no chance to insert
        // an interceptor between a networking thread being started and a resource
        // request being issued.
        return message_loop == nullptr || message_loop->type() == base::MessageLoop::TYPE_IO;
    }

    // When removing interceptors, DCHECK that this function returns true.
    bool OnMessageLoopForInterceptorRemoval()
    {
        // Checking for a MessageLoopForIO is a best effort at determining whether the
        // current thread is a networking thread.
        return base::MessageLoopForIO::IsCurrent();
    }

} // namespace

URLRequestFilter* URLRequestFilter::shared_instance_ = NULL;

// static
URLRequestFilter* URLRequestFilter::GetInstance()
{
    DCHECK(OnMessageLoopForInterceptorAddition());
    if (!shared_instance_)
        shared_instance_ = new URLRequestFilter;
    return shared_instance_;
}

void URLRequestFilter::AddHostnameInterceptor(
    const std::string& scheme,
    const std::string& hostname,
    std::unique_ptr<URLRequestInterceptor> interceptor)
{
    DCHECK(OnMessageLoopForInterceptorAddition());
    DCHECK_EQ(0u, hostname_interceptor_map_.count(make_pair(scheme, hostname)));
    hostname_interceptor_map_[make_pair(scheme, hostname)] = std::move(interceptor);

#ifndef NDEBUG
    // Check to see if we're masking URLs in the url_interceptor_map_.
    for (const auto& pair : url_interceptor_map_) {
        const GURL& url = GURL(pair.first);
        HostnameInterceptorMap::const_iterator host_it = hostname_interceptor_map_.find(make_pair(url.scheme(), url.host()));
        if (host_it != hostname_interceptor_map_.end())
            NOTREACHED();
    }
#endif // !NDEBUG
}

void URLRequestFilter::RemoveHostnameHandler(const std::string& scheme,
    const std::string& hostname)
{
    DCHECK(OnMessageLoopForInterceptorRemoval());
    int removed = hostname_interceptor_map_.erase(make_pair(scheme, hostname));
    DCHECK(removed);

    // Note that we don't unregister from the URLRequest ProtocolFactory as
    // this would leave no protocol factory for the remaining hostname and URL
    // handlers.
}

bool URLRequestFilter::AddUrlInterceptor(
    const GURL& url,
    std::unique_ptr<URLRequestInterceptor> interceptor)
{
    DCHECK(OnMessageLoopForInterceptorAddition());
    if (!url.is_valid())
        return false;
    DCHECK_EQ(0u, url_interceptor_map_.count(url.spec()));
    url_interceptor_map_[url.spec()] = std::move(interceptor);

    // Check to see if this URL is masked by a hostname handler.
    DCHECK_EQ(0u, hostname_interceptor_map_.count(make_pair(url.scheme(), url.host())));

    return true;
}

void URLRequestFilter::RemoveUrlHandler(const GURL& url)
{
    DCHECK(OnMessageLoopForInterceptorRemoval());
    size_t removed = url_interceptor_map_.erase(url.spec());
    DCHECK(removed);
    // Note that we don't unregister from the URLRequest ProtocolFactory as
    // this would leave no protocol factory for the remaining hostname and URL
    // handlers.
}

void URLRequestFilter::ClearHandlers()
{
    DCHECK(OnMessageLoopForInterceptorRemoval());
    url_interceptor_map_.clear();
    hostname_interceptor_map_.clear();
    hit_count_ = 0;
}

URLRequestJob* URLRequestFilter::MaybeInterceptRequest(
    URLRequest* request,
    NetworkDelegate* network_delegate) const
{
    DCHECK(base::MessageLoopForIO::current());
    URLRequestJob* job = NULL;
    if (!request->url().is_valid())
        return NULL;

    // Check the hostname map first.
    const std::string hostname = request->url().host();
    const std::string scheme = request->url().scheme();

    {
        HostnameInterceptorMap::const_iterator it = hostname_interceptor_map_.find(make_pair(scheme, hostname));
        if (it != hostname_interceptor_map_.end())
            job = it->second->MaybeInterceptRequest(request, network_delegate);
    }

    if (!job) {
        // Not in the hostname map, check the url map.
        const std::string& url = request->url().spec();
        URLInterceptorMap::const_iterator it = url_interceptor_map_.find(url);
        if (it != url_interceptor_map_.end())
            job = it->second->MaybeInterceptRequest(request, network_delegate);
    }
    if (job) {
        DVLOG(1) << "URLRequestFilter hit for " << request->url().spec();
        hit_count_++;
    }
    return job;
}

URLRequestFilter::URLRequestFilter()
    : hit_count_(0)
{
    DCHECK(OnMessageLoopForInterceptorAddition());
    URLRequestJobFactoryImpl::SetInterceptorForTesting(this);
}

URLRequestFilter::~URLRequestFilter()
{
    DCHECK(OnMessageLoopForInterceptorRemoval());
    URLRequestJobFactoryImpl::SetInterceptorForTesting(NULL);
}

} // namespace net
